package cn.dreampie.common.util;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Created by wangrenhui on 14/12/19.
 */
public class ParamNamesScaner {

  /**
   * 获取Method的形参名称列表
   *
   * @param method 需要解析的方法
   * @return 形参名称列表, 如果没有调试信息, 将返回null
   */
  public static List<String> getParamNames(Method method) {
    try {
      int size = method.getParameterTypes().length;
      if (size == 0)
        return new ArrayList<String>(0);
      List<String> list = getParamNames(method.getDeclaringClass()).get(getKey(method));
      if (list != null && list.size() != size)
        return list.subList(0, size);
      return list;
    } catch (Throwable e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 获取Constructor的形参名称列表
   *
   * @param constructor 需要解析的构造函数
   * @return 形参名称列表, 如果没有调试信息, 将返回null
   */
  public static List<String> getParamNames(Constructor<?> constructor) {
    try {
      int size = constructor.getParameterTypes().length;
      if (size == 0)
        return new ArrayList<String>(0);
      List<String> list = getParamNames(constructor.getDeclaringClass())
          .get(getKey(constructor));
      if (list != null && list.size() != size)
        return list.subList(0, size);
      return list;
    } catch (Throwable e) {
      throw new RuntimeException(e);
    }
  }

  // ---------------------------------------------------------------------------------------------------

  /**
   * 获取一个类的所有方法/构造方法的形参名称Map
   *
   * @param klass 需要解析的类
   * @return 所有方法/构造方法的形参名称Map
   * @throws java.io.IOException 如果有任何IO异常,不应该有,如果是本地文件,那100%遇到bug了
   */
  public static Map<String, List<String>> getParamNames(Class<?> klass)
      throws IOException {
    InputStream in = klass.getResourceAsStream("/"
        + klass.getName().replace('.', '/') + ".class");
    return getParamNames(in);
  }

  public static Map<String, List<String>> getParamNames(InputStream in)
      throws IOException {
    DataInputStream dis = new DataInputStream(new BufferedInputStream(in));
    Map<String, List<LocalVariable>> names = new HashMap<String, List<LocalVariable>>();
    Map<Integer, String> strs = new HashMap<Integer, String>();
    dis.skipBytes(4);// Magic
    dis.skipBytes(2);// 副版本号
    dis.skipBytes(2);// 主版本号

    // 读取常量池
    int constant_pool_count = dis.readUnsignedShort();
    for (int i = 0; i < (constant_pool_count - 1); i++) {
      byte flag = dis.readByte();
      switch (flag) {
        case 7:// CONSTANT_Class:
          dis.skipBytes(2);
          break;
        case 9:// CONSTANT_Fieldref:
        case 10:// CONSTANT_Methodref:
        case 11:// CONSTANT_InterfaceMethodref:
          dis.skipBytes(2);
          dis.skipBytes(2);
          break;
        case 8:// CONSTANT_String:
          dis.skipBytes(2);
          break;
        case 3:// CONSTANT_Integer:
        case 4:// CONSTANT_Float:
          dis.skipBytes(4);
          break;
        case 5:// CONSTANT_Long:
        case 6:// CONSTANT_Double:
          dis.skipBytes(8);
          i++;// 必须跳过一个,这是class文件设计的一个缺陷,历史遗留问题
          break;
        case 12:// CONSTANT_NameAndType:
          dis.skipBytes(2);
          dis.skipBytes(2);
          break;
        case 1:// CONSTANT_Utf8:
          int len = dis.readUnsignedShort();
          byte[] data = new byte[len];
          dis.read(data);
          strs.put(i + 1, new String(data, "UTF-8"));// 必然是UTF8的
          break;
        case 15:// CONSTANT_MethodHandle:
          dis.skipBytes(1);
          dis.skipBytes(2);
          break;
        case 16:// CONSTANT_MethodType:
          dis.skipBytes(2);
          break;
        case 18:// CONSTANT_InvokeDynamic:
          dis.skipBytes(2);
          dis.skipBytes(2);
          break;
        default:
          throw new RuntimeException("Impossible!! flag=" + flag);
      }
    }

    dis.skipBytes(2);// 版本控制符
    dis.skipBytes(2);// 类名
    dis.skipBytes(2);// 超类

    // 跳过接口定义
    int interfaces_count = dis.readUnsignedShort();
    dis.skipBytes(2 * interfaces_count);// 每个接口数据,是2个字节

    // 跳过字段定义
    int fields_count = dis.readUnsignedShort();
    for (int i = 0; i < fields_count; i++) {
      dis.skipBytes(2);
      dis.skipBytes(2);
      dis.skipBytes(2);
      int attributes_count = dis.readUnsignedShort();
      for (int j = 0; j < attributes_count; j++) {
        dis.skipBytes(2);// 跳过访问控制符
        int attribute_length = dis.readInt();
        dis.skipBytes(attribute_length);
      }
    }

    // 开始读取方法
    int methods_count = dis.readUnsignedShort();
    for (int i = 0; i < methods_count; i++) {
      dis.skipBytes(2); // 跳过访问控制符
      String methodName = strs.get(dis.readUnsignedShort());
      String descriptor = strs.get(dis.readUnsignedShort());
      short attributes_count = dis.readShort();
      for (int j = 0; j < attributes_count; j++) {
        String attrName = strs.get(dis.readUnsignedShort());
        int attribute_length = dis.readInt();
        if ("Code".equals(attrName)) { // 形参只在Code属性中
          dis.skipBytes(2);
          dis.skipBytes(2);
          int code_len = dis.readInt();
          dis.skipBytes(code_len); // 跳过具体代码
          int exception_table_length = dis.readUnsignedShort();
          dis.skipBytes(8 * exception_table_length); // 跳过异常表

          int code_attributes_count = dis.readUnsignedShort();
          for (int k = 0; k < code_attributes_count; k++) {
            int str_index = dis.readUnsignedShort();
            String codeAttrName = strs.get(str_index);
            int code_attribute_length = dis.readInt();
            if ("LocalVariableTable".equals(codeAttrName)) {// 形参在LocalVariableTable属性中
              int local_variable_table_length = dis
                  .readUnsignedShort();
              List<LocalVariable> varNames = new ArrayList<LocalVariable>(
                  local_variable_table_length);
              for (int l = 0; l < local_variable_table_length; l++) {
                dis.skipBytes(2);
                dis.skipBytes(2);
                String varName = strs.get(dis
                    .readUnsignedShort());
                dis.skipBytes(2);
                // index
                int index = dis.readUnsignedShort();
                if (!"this".equals(varName)) // 非静态方法,第一个参数是this
                  varNames.add(new LocalVariable(index,
                      varName));
              }
              names.put(methodName + "," + descriptor, varNames);
            } else
              dis.skipBytes(code_attribute_length);
          }
        } else
          dis.skipBytes(attribute_length);
      }
    }
    dis.close();

    Set<String> set = names.keySet();
    Map<String, List<String>> reMap = new HashMap<String, List<String>>();
    for (String key : set) {
      LocalVariable[] tmpArr = names.get(key).toArray(
          new LocalVariable[0]);
      Arrays.sort(tmpArr);
      List<String> list = new ArrayList<String>();
      for (LocalVariable var : tmpArr) {
        list.add(var.name);
      }
      reMap.put(key, list);
    }
    return reMap;
  }

  static class LocalVariable implements Comparable<LocalVariable> {
    public int index;
    public String name;

    public LocalVariable(int index, String name) {
      this.index = index;
      this.name = name;
    }

    public int compareTo(LocalVariable o) {
      return this.index - o.index;
    }
  }

  /**
   * 传入Method或Constructor,获取getParamNames方法返回的Map所对应的key
   */
  public static String getKey(Object obj) {
    StringBuilder sb = new StringBuilder();
    if (obj instanceof Method) {
      sb.append(((Method) obj).getName()).append(',');
      getDescriptor(sb, (Method) obj);
    } else if (obj instanceof Constructor) {
      sb.append("<init>,"); // 只有非静态构造方法才能用有方法参数的,而且通过反射API拿不到静态构造方法
      getDescriptor(sb, (Constructor<?>) obj);
    } else
      throw new RuntimeException("Not Method or Constructor!");
    return sb.toString();
  }

  public static void getDescriptor(StringBuilder sb, Method method) {
    sb.append('(');
    for (Class<?> klass : method.getParameterTypes())
      getDescriptor(sb, klass);
    sb.append(')');
    getDescriptor(sb, method.getReturnType());
  }

  public static void getDescriptor(StringBuilder sb,
                                   Constructor<?> constructor) {
    sb.append('(');
    for (Class<?> klass : constructor.getParameterTypes())
      getDescriptor(sb, klass);
    sb.append(')');
    sb.append('V');
  }

  /**
   * 本方法来源于ow2的asm库的Type类
   */
  public static void getDescriptor(final StringBuilder buf, final Class<?> c) {
    Class<?> d = c;
    while (true) {
      if (d.isPrimitive()) {
        char car;
        if (d == Integer.TYPE) {
          car = 'I';
        } else if (d == Void.TYPE) {
          car = 'V';
        } else if (d == Boolean.TYPE) {
          car = 'Z';
        } else if (d == Byte.TYPE) {
          car = 'B';
        } else if (d == Character.TYPE) {
          car = 'C';
        } else if (d == Short.TYPE) {
          car = 'S';
        } else if (d == Double.TYPE) {
          car = 'D';
        } else if (d == Float.TYPE) {
          car = 'F';
        } else /* if (d == Long.TYPE) */ {
          car = 'J';
        }
        buf.append(car);
        return;
      } else if (d.isArray()) {
        buf.append('[');
        d = d.getComponentType();
      } else {
        buf.append('L');
        String name = d.getName();
        int len = name.length();
        for (int i = 0; i < len; ++i) {
          char car = name.charAt(i);
          buf.append(car == '.' ? '/' : car);
        }
        buf.append(';');
        return;
      }
    }
  }
}
